#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "hashtable.h"
#include "symbol.h"
#include "parser.h"
#include "libblogc/list.h"
#include "node.h"

static void * symbol_table;

static unsigned long symbol_hash(void * symbol);
static int symbol_cmp(void * symbol1, void * symbol2);

void symbol_table_alloc(void)
{
  void * iter;
  symbol * sym;
  
  symbol_table = hashtable_alloc(1000, 2.0, symbol_hash, symbol_cmp);

  /* load the built-in types, functions (TODO), and objects */

  /* Boolean */
  symbol_table_new_type("Boolean");
  sym = symbol_table_find("Boolean");
  symbol_table_new_object(sym, "true");  /* true comes before false */
  symbol_table_new_object(sym, "false");
  sym->t.has_guarobjs = 1;
  
  /* C_Boolean is different from Boolean in that true is 1 */
  symbol_table_new_type("C_Boolean");
  sym = symbol_table_find("C_Boolean");
  sym->t.infinite = 1;
  
  symbol_table_new_type("NaturalNum");
  sym =  symbol_table_find("NaturalNum");
  sym->t.infinite = 1;
  
  symbol_table_new_type("Real");
  sym = symbol_table_find("Real");
  sym->t.infinite = 1;

  symbol_table_new_type("String");
  sym = symbol_table_find("String");
  sym->t.infinite = 1;

  /* mark all the above symbols as builtin */
  for(iter = hashtable_itr_start(symbol_table);
      (sym = (symbol *)hashtable_itr_next(iter));)
  {
    sym->builtin = 1;
  }
  hashtable_itr_end(iter);
}

void symbol_table_free(void)
{
  hashtable_free(symbol_table);
  symbol_table = NULL;
}

void symbol_print(symbol * sym)
{
  list_t iter;
  
  printf("---------------------- %s -----------------------------\n",
         sym->name);

  switch(sym->symtype)
  {
  case SYMBOL_B_TYPE:
    if (sym->t.has_guarobjs)
      printf("(guaranteed type) declname %s  %d objects\n", 
             sym->t.gendeclname, list_count(sym->t.objlist));
    
    else if (sym->t.has_obsobjs)
      printf("(observed type) %d objects\n", list_count(sym->t.objlist));
    
    else if (sym->t.infinite)
      printf("(infinite)\n");

    
    printf("%d origin fns, %d attrs\n", list_count(sym->t.origfns),
           list_count(sym->t.attrs));

    if (list_count(sym->t.objlist))
    {
      printf("Objects:");
      for (iter = list_next(sym->t.objlist); iter; 
           iter = list_next(iter))
      {
        printf(" %s", ((symbol *)list_item(iter))->name);
      }
      printf("\n");
    }

    if (list_count(sym->t.origfns))
    {
      printf("Origin:");
      for (iter = list_next(sym->t.origfns); iter; 
           iter = list_next(iter))
      {
        printf(" %s", ((symbol *)list_item(iter))->name);
      }
      printf("\n");
    }

    if (list_count(sym->t.attrs))
    {
      printf("Attrs:");
      for (iter = list_next(sym->t.attrs); iter; 
           iter = list_next(iter))
      {
        printf(" %s", ((symbol *)list_item(iter))->name);
      }
      printf("\n");
    }

    if (!list_is_empty(sym->t.incoming_fns))
    {
      list_t iter2;
      printf("Incoming Functions:");
      for (iter2 = list_next(sym->t.incoming_fns); iter2;
           iter2 = list_next(iter2))
        printf(" %s", ((symbol *)list_item(iter2))->name);
      printf("\n");
    }
    
    if (sym->t.null_numfn)
      printf("null number function: %s\n", sym->t.null_numfn->name);

    if (!list_is_empty(sym->t.childfns))
    {
      list_t iter2;
      printf("Child Functions:");
      for(iter2 = list_next(sym->t.childfns); iter2;
          iter2 = list_next(iter2))
        printf(" %s", ((symbol *)list_item(iter2))->name);
      printf("\n");
    }

    break;

  case SYMBOL_B_FUNCTION:
    switch(sym->f.category)
    {
    case SYMBOL_FN_CAT_RANDOM:
      printf("(random ");
      break;

    case SYMBOL_FN_CAT_NONRANDOM:
      printf("(non-random ");
      break;

    case SYMBOL_FN_CAT_ORIGIN:
      printf("(origin ");
      break;

    default:
      printf("(unknown category ");
      break;
    }
    printf("function) returns %s argtypes:", 
           sym->f.rettype->name);
    
    for(iter = list_next(sym->f.argtype_list); iter;
        iter = list_next(iter))
    {
      printf(" %s", ((symbol *)list_item(iter))->name);
    }
    
    if (sym->f.argname_list)
    {
      printf(" argnames:");
      for(iter = list_next(sym->f.argname_list); iter;
          iter = list_next(iter))
      {
        node_print((node *) list_item(iter));
      }
    }
    
    printf("\n");
    
    if (sym->f.numfn)
      printf("number function: %s\n", sym->f.numfn->name);

    if (sym->f.tree)
    {
      node_print(sym->f.tree);
      printf("\n");
    }
    if (sym->f.zary_obs)
    {
      printf("  Observation = ");
      node_print(sym->f.zary_obs);
      printf("\n");
    }
    else if (sym->f.has_obs)
    {
      printf("  Some Observation.\n");
    }

    if (!list_is_empty(sym->f.childfns))
    {
      list_t iter2;
      printf("Children Functions:");
      for (iter2 = list_next(sym->f.childfns); iter2;
           iter2 = list_next(iter2))
        printf(" %s", ((symbol *)list_item(iter2))->name);
      printf("\n");
    }
    if (!list_is_empty(sym->f.childattrs))
    {
      list_t iter2;
      printf("Children Attributes:");
      for (iter2 = list_next(sym->f.childattrs); iter2;
           iter2 = list_next(iter2))
        printf(" %s", ((symbol *)list_item(iter2))->name);
      printf("\n");
    }
    if (!list_is_empty(sym->f.child_in_attrs))
    {
      list_t iter2;
      printf("Children Incoming Attributes:\n");
      for (iter2 = list_next(sym->f.child_in_attrs); iter2;
           iter2 = list_next(iter2))
      {
        list_t iter3;
        printf("  ( ");
        for (iter3 = list_next(list_item(iter2)); iter3;
             iter3 = list_next(iter3))
          printf("%s ", ((symbol *)list_item(iter3))->name);
        printf(")\n");
      }
    }
    if (!list_is_empty(sym->f.block_child))
    {
      list_t iter2;
      printf("Blocking Child Attributes:");
      for (iter2 = list_next(sym->f.block_child); iter2;
           iter2 = list_next(iter2))
        printf(" %s", ((symbol *)list_item(iter2))->name);
      printf("\n");      
    }
    
    if (sym->f.cpdtype.uncond_uniform)
    {
      printf("CPD is unconditional UniformChoice\n");
    }
    break;
  case SYMBOL_B_OBJECT:
    printf("(object) type: %s  %d obs\n", sym->o.objtype->name,
           list_count(sym->o.obs_attr_vals));
    if (!list_is_empty(sym->o.obs_attr_vals))
    {
      list_t iter2;
      
      printf("  Observations:\n");
      
      for (iter2 = list_next(sym->o.obs_attr_vals); iter2;
           iter2 = list_next(iter2))
      {
        symbol * fnsym;
        node * valnode;
        
        list_item2(list_item(iter2), (void **)&fnsym, (void **)&valnode);
        printf("  %s = ", fnsym->name);
        node_print(valnode);
        printf("\n");
      }
    }
    
    break;
  }
}

void * symbol_table_itr_start(void)
{
  return hashtable_itr_start(symbol_table);
}

void symbol_table_print(void)
{
  void * iter;
  symbol * sym;
  
  for(iter = hashtable_itr_start(symbol_table);
      (sym = (symbol *)hashtable_itr_next(iter));)
  {
    if (sym->builtin)
      continue;
 
    symbol_print(sym);
  }
  hashtable_itr_end(iter);
}

/* returns 1 if the type already exists 
   name - pointer to name
   (on success, name belongs to the symbol table; on failure, it belongs to the
   caller)
 */
int symbol_table_new_type(char * name)
{
  symbol * sym;
  /* allocate and initialize a new symbol for the type */
  sym = (symbol *) calloc(1, sizeof(*sym));
  sym->name = name;
  sym->symtype = SYMBOL_B_TYPE;
  
  sym->t.objlist = list_alloc();
  sym->t.origfns = list_alloc();
  sym->t.attrs = list_alloc();
  sym->t.childfns = list_alloc();
  sym->t.incoming_fns = list_alloc();
  
  /* try to install this symbol in the symbol table */
  if(hashtable_insert(symbol_table, (void *) sym))
  {
    list_free(sym->t.objlist);
    list_free(sym->t.origfns);
    list_free(sym->t.attrs);
    list_free(sym->t.incoming_fns);
    
    free(sym);
    return 1;
  }
  else
    return 0;
}

/* returns 1 if the type already exists 
   (on success, ptrs belongs to the symbol table; on failure, it belongs to the
   caller)
 */
int symbol_table_new_function_decl(int category, symbol * ret_type,
                                   char * name, void * argtype_list)
{
  symbol * fnsym;

  fnsym = (symbol *)calloc(1, sizeof(*fnsym));

  /* initialize the base symbol */
  fnsym->name = name;
  fnsym->symtype = SYMBOL_B_FUNCTION;
  
  /* initialize the function */
  fnsym->f.category = category;
  fnsym->f.rettype = ret_type;
  fnsym->f.argtype_list = argtype_list;
  fnsym->f.childfns = list_alloc();
  fnsym->f.childattrs = list_alloc();
  fnsym->f.child_in_attrs = list_alloc();
  fnsym->f.block_child = list_alloc();

  /* try to install this symbol in the symbol table */
  if(hashtable_insert(symbol_table, (void *) fnsym))
  {
    free(fnsym);
    return 1;
  }
  else
    return 0;
}

/* returns 1 if the object already exists 
   (on success, ptrs belongs to the symbol table; on failure, it belongs to the
   caller)
 */
int symbol_table_new_object(symbol * obj_type, char * obj_name)
{
  symbol * objsym;
  
  objsym = (symbol *)calloc(1, sizeof(*objsym));
  
  /* initialize the base symbol */
  objsym->name = obj_name;
  objsym->symtype = SYMBOL_B_OBJECT;

  /* the symbol for the object type better better be a type symbol */
  assert(SYMBOL_B_TYPE == obj_type->symtype);

  /* initialize the object symbol */
  objsym->o.objtype = obj_type;
  
  /* try to install this symbol in the symbol table */
  if(hashtable_insert(symbol_table, (void *) objsym))
  {
    free(objsym);
    return 1;
  }

  /* give a number to this object */
  objsym->o.objnum = list_count(obj_type->t.objlist);
  list_insert_end(obj_type->t.objlist, objsym);


  objsym->o.obs_attr_vals = list_alloc();
  
  return 0;
}

/*
  finds a symbol in the table or NULL
  name - pointer to name
*/
symbol * symbol_table_find(char * name)
{
  symbol * sym;
  symbol * retval;
  int k;                                     /* the k of RkVector */
  
  sym = (symbol *) malloc(sizeof(*sym));
  sym->name = name;
  
  retval = (symbol *) hashtable_find(symbol_table, (void *)sym);

  /* create a new RkVector (for k>=2) if such a type doesn't
   * already exist */
  k = -1;
  if ((NULL == retval) && (1 == sscanf(name, "R%dVector", &k)) && (k>=2))
  {
    char * newname;
    int typeexists;
    
    /* allocate and initialize the name of the type */
    newname = (char *)malloc(sizeof(*newname) * (strlen(name)+1));
    strcpy(newname, name);
    typeexists = symbol_table_new_type(newname);
    assert(0 == typeexists); /* shouldn't exist */
    retval = hashtable_find(symbol_table, (void *) sym); /* find RkVector */
    assert(NULL != retval);                  /* should exist */
    retval->builtin = 1;
    retval->t.infinite = 1;
  }
  free(sym);
  
  return retval;
}

/*
  gets the type or exits with an error
*/
symbol * symbol_table_gettype(char * typename)
{
  symbol * sym;
  
  sym = symbol_table_find(typename);
  if (!sym || (SYMBOL_B_TYPE != sym->symtype))
  {
    fprintf(stderr, "Error: unknown type %s", typename);
    exit(1);
  }
  
  return sym;
}


/*
  gets the obj name or exits with an error
*/
symbol * symbol_table_getobj(char * objname)
{
  symbol * sym;
  
  sym = symbol_table_find(objname);
  if (!sym || (SYMBOL_B_OBJECT != sym->symtype))
  {
    fprintf(stderr, "Error: unknown object %s", objname);
    exit(1);
  }
  
  return sym;
}


/*
  gets the function name or exits with an error
*/
symbol * symbol_table_getfn(char * fnname)
{
  symbol * sym;
  
  sym = symbol_table_find(fnname);
  if (!sym || (SYMBOL_B_FUNCTION != sym->symtype))
  {
    fprintf(stderr, "Error: unknown function %s", fnname);
    exit(1);
  }
  
  return sym;
}



static unsigned long symbol_hash(void * sym)
{
  return hashtable_djb2((unsigned char *) ((symbol *) sym)->name, 0);
}

static int symbol_cmp(void * symbol1, void * symbol2)
{
  return strcmp(((symbol *) symbol1)->name,
                ((symbol *) symbol1)->name);
}


